import { Matrix4 } from "../../utils/Matrix4";
import { Primitives } from "../../utils/Primitives";
import { TextrueUtil } from "../../utils/TextureUtil";
import { Util } from "../../utils/Util";
import { WebGLUtil } from "../../utils/WebGLUtil";
import { IProgramInfo } from "../interface/IDefine";
import { Render } from "../Render";
import { WebglTemplateBase } from "./WebglTemplateBase";

/*
 * @Author: Snow
 * @Date: 2021-10-13 09:53:20
 * @Description: 阴影示例 方向光
 */
export class TemplateShader2 extends WebglTemplateBase {

    private colorProgramInfo: {
        program: WebGLProgram,
        uniformSetters: { [name: string]: Function },
        attribSetters: { [name: string]: Function },
    };

    protected override initUITools(): void {
        const gl = Render.GL;
        let translation = [200, 150];
        let scale = [1, 1];
        this.uiParam = {
            cameraX: 6,
            cameraY: 5,
            posX: 3.5,
            posY: 4.4,
            posZ: 4.7,
            targetX: 0.8,
            targetY: 0,
            targetZ: 4.7,
            projWidth: 1,
            projHeight: 1,
            perspective: true,
            fieldOfView: 45,
            bais:-0.06,
        }
        // Setup a ui.
        this.createSlider("cameraX", { value: this.uiParam.cameraX, slide: this.onUpateSlide(this, "cameraX"), min: -10, max: 10, step: 0.1, precision: 1 })
        this.createSlider("cameraY", { value: this.uiParam.cameraY, slide: this.onUpateSlide(this, "cameraY"), min: 1, max: 20, step: 0.1, precision: 1 })
        this.createSlider("posX", { value: this.uiParam.posX, slide: this.onUpateSlide(this, "posX"), min: -10, max: 10, step: 0.03, precision: 2 })
        this.createSlider("posY", { value: this.uiParam.posY, slide: this.onUpateSlide(this, "posY"), min: 1, max: 20, step: 0.1, precision: 1 })
        this.createSlider("posZ", { value: this.uiParam.posZ, slide: this.onUpateSlide(this, "posZ"), min: 1, max: 20, step: 0.1, precision: 1 })
        this.createSlider("targetX", { value: this.uiParam.targetX, slide: this.onUpateSlide(this, "targetX"), min: -10, max: 10, step: 0.1, precision: 1 })
        this.createSlider("targetY", { value: this.uiParam.targetY, slide: this.onUpateSlide(this, "targetY"), min: 0, max: 20, step: 0.1, precision: 1 })
        this.createSlider("targetZ", { value: this.uiParam.targetZ, slide: this.onUpateSlide(this, "targetZ"), min: -10, max: 20, step: 0.1, precision: 1 })
        this.createSlider("projWidth", { value: this.uiParam.projWidth, slide: this.onUpateSlide(this, "projWidth"), min: 1, max: 10, step: 0.1, precision: 1 })
        this.createSlider("projHeight", { value: this.uiParam.projHeight, slide: this.onUpateSlide(this, "projHeight"), min: 1, max: 10, step: 0.1, precision: 1 })
        this.createTabel("perspective", "是否是透视矩阵", true);
        this.createSlider("fieldOfView", { value: this.uiParam.fieldOfView, slide: this.onUpateSlide(this, "fieldOfView"), min: 1, max: 180, step: 1, precision: 0 })
        this.createSlider("bais", { value: this.uiParam.bais, slide: this.onUpateSlide(this, "bais"), min: -0.01, max: 0.0000001, step: 0.001, precision: 4 })
    }

    protected override initProgram(): void {
        this.vsSource = `
            // 顶点着色器
            attribute vec4 a_position;
            attribute vec2 a_texcoord;
            attribute vec3 a_normal;
            
         
            uniform mat4 u_projection;
            uniform mat4 u_view;
            uniform mat4 u_world;
            uniform mat4 u_textureMatrix;
            
            varying vec2 v_texcoord;
            varying vec4 v_projectedTexcoord;

            varying vec3 v_normal;
 
            void main() {
                vec4 worldPosition = u_world * a_position;
                
                gl_Position = u_projection * u_view * worldPosition;

                // 把纹理坐标传给片段着色器
                v_texcoord = a_texcoord;

                v_projectedTexcoord = u_textureMatrix * worldPosition;
                
                v_normal = mat3(u_world) * a_normal;
            }
        `;

        this.fsSource = `
            // 片段着色器
            precision mediump float;
            
            // 从顶点着色器传来的
            varying vec2 v_texcoord;
            varying vec4 v_projectedTexcoord;

            varying vec3 v_normal;
         
            
            uniform vec4 u_colorMult;
            uniform sampler2D u_texture;
            uniform sampler2D u_projectedTexture;

            uniform float u_bias;

            uniform vec3 u_reverseLightDirection;


            void main() {
                // 因为 v_normal 是一个 varying，它已经被插值了
                // 所以它不会是一个单位向量。对它进行归一化
                // 使其再次成为单位向量
                vec3 normal = normalize(v_normal);
                float light = dot(normal, u_reverseLightDirection);
                

                // 除以 w 得到正确的值，详见透视投影的文章
                vec3 projectedTexcoord = v_projectedTexcoord.xyz / v_projectedTexcoord.w;
                float currentDepth = projectedTexcoord.z + u_bias;
                bool inRange = 
                    projectedTexcoord.x >= 0.0 &&
                    projectedTexcoord.x <= 1.0 &&
                    projectedTexcoord.y >= 0.0 &&
                    projectedTexcoord.y <= 1.0;
                
                //vec4 projectedTexColor = texture2D(u_projectedTexture, projectedTexcoord.xy);
                //vec4 projectedTexColor = vec4(texture2D(u_projectedTexture, projectedTexcoord.xy).rrr,1);
                float projectedDepth = texture2D(u_projectedTexture, projectedTexcoord.xy).r;
                float shadowLight = (inRange && projectedDepth<=currentDepth) ? 0.0:1.0;
                
                vec4 texColor = texture2D(u_texture, v_texcoord) * u_colorMult;

                float projectedAmount = inRange ? 1.0 : 0.0;
                vec4 texureColor = texture2D(u_texture, v_texcoord) * u_colorMult;
                //gl_FragColor = vec4(texureColor.rgb * shadowLight,texureColor.a);
                //gl_FragColor = mix(texColor, projectedTexColor, projectedAmount);
                //gl_FragColor = texture2D(u_texture, v_texcoord) * u_colorMult;
                
                gl_FragColor = vec4(
                    texColor.rgb * light * shadowLight,
                    texColor.a);

            }
        `;
        super.initProgram();
        /* 相机顶点 */
        const cameraVsSource = `
            attribute vec4 a_position;
    
            uniform mat4 u_projection;
            uniform mat4 u_view;
            uniform mat4 u_world;
            
            void main() {
                // 将 position 乘以矩阵
                gl_Position = u_projection * u_view * u_world * a_position;
            }
        `;

        const cameraFsSource = `
            precision mediump float;
    
            uniform vec4 u_color;
            
            void main() {
                gl_FragColor = u_color;
            }
        `;
        this.colorProgramInfo = WebGLUtil.createProgramInfo(Render.GL, cameraVsSource, cameraFsSource);
    }

    protected override initBuffers(): void {
        const gl = Render.GL;
        ///开启该扩展，它会给我们提供深度纹理
        const ext = gl.getExtension('WEBGL_depth_texture');
        if (!ext) {
            return;
        }

        super.initBuffers();

        gl.enable(gl.CULL_FACE);

        const sphereBufferInfo = Primitives.createBufferInfoFunc(Primitives.createSphereVertices)(
            gl,
            1,  // 半径
            32, // 横轴细分数
            24,  // 纵轴细分数
        );

        const planeBufferInfo = Primitives.createBufferInfoFunc(Primitives.createPlaneVertices)(
            gl,
            20,  // 宽
            20,  // 高
            1,   // 横轴细分数
            1,   // 纵轴细分数
        );

        const cubeLinesBufferInfo = WebGLUtil.createBufferInfoFromArrays(gl, {
            position: [
                -1, -1, -1,
                1, -1, -1,
                -1, 1, -1,
                1, 1, -1,
                -1, -1, 1,
                1, -1, 1,
                -1, 1, 1,
                1, 1, 1,
            ],
            indices: [
                0, 1,
                1, 3,
                3, 2,
                2, 0,

                4, 5,
                5, 7,
                7, 6,
                6, 4,

                0, 4,
                1, 5,
                3, 7,
                2, 6,
            ],
        });

        const cubeBufferInfo = Primitives.createBufferInfoFunc(Primitives.createCubeVertices)(
            gl,
            2,  // size
        );

        // 创建一个 8x8 棋盘纹理
        const checkerboardTexture = gl.createTexture();
        gl.bindTexture(gl.TEXTURE_2D, checkerboardTexture);
        gl.texImage2D(
            gl.TEXTURE_2D,
            0,                // mip level
            gl.LUMINANCE,     // internal format
            8,                // width
            8,                // height
            0,                // border
            gl.LUMINANCE,     // format
            gl.UNSIGNED_BYTE, // type
            new Uint8Array([  // data
                0xFF, 0xCC, 0xFF, 0xCC, 0xFF, 0xCC, 0xFF, 0xCC,
                0xCC, 0xFF, 0xCC, 0xFF, 0xCC, 0xFF, 0xCC, 0xFF,
                0xFF, 0xCC, 0xFF, 0xCC, 0xFF, 0xCC, 0xFF, 0xCC,
                0xCC, 0xFF, 0xCC, 0xFF, 0xCC, 0xFF, 0xCC, 0xFF,
                0xFF, 0xCC, 0xFF, 0xCC, 0xFF, 0xCC, 0xFF, 0xCC,
                0xCC, 0xFF, 0xCC, 0xFF, 0xCC, 0xFF, 0xCC, 0xFF,
                0xFF, 0xCC, 0xFF, 0xCC, 0xFF, 0xCC, 0xFF, 0xCC,
                0xCC, 0xFF, 0xCC, 0xFF, 0xCC, 0xFF, 0xCC, 0xFF,
            ]));
        gl.generateMipmap(gl.TEXTURE_2D);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);

        const fTextrue = this.loadImageTexture("f-texture.png");

        // 平面 uniforms
        const planeUniforms = {
            u_colorMult: [0.5, 0.5, 1, 1],  // 浅蓝色
            u_texture: checkerboardTexture,
            u_world: Matrix4.translation(0, 0, 0),
        };

        // 球体
        const sphereUniforms = {
            u_colorMult: [1, 0.5, 0.5, 1],  // 粉红色
            u_texture: checkerboardTexture,
            u_world: Matrix4.translation(2, 3, 4),
        };

        ///立方体
        const cubeUniforms  = {
            u_colorMult: [0.5, 1, 0.5, 1],  // 粉红色
            u_color: [0, 0, 1, 1],
            u_texture: checkerboardTexture,
            u_world: Matrix4.translation(3, 1, 0),
        };



        const depthTexture = gl.createTexture();
        const depthTextureSize = 512;
        gl.bindTexture(gl.TEXTURE_2D, depthTexture);
        gl.texImage2D(
            gl.TEXTURE_2D,      // target
            0,                  // mip level
            gl.DEPTH_COMPONENT, // internal format
            depthTextureSize,   // width
            depthTextureSize,   // height
            0,                  // border
            gl.DEPTH_COMPONENT, // format
            gl.UNSIGNED_INT,    // type
            null);              // data
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);

        const depthFramebuffer = gl.createFramebuffer();
        gl.bindFramebuffer(gl.FRAMEBUFFER, depthFramebuffer);
        gl.framebufferTexture2D(
            gl.FRAMEBUFFER,       // target
            gl.DEPTH_ATTACHMENT,  // attachment point
            gl.TEXTURE_2D,        // texture target
            depthTexture,         // texture
            0);                   // mip level

        // 创建一个和深度纹理相同尺寸的颜色纹理
        const unusedTexture = gl.createTexture();
        gl.bindTexture(gl.TEXTURE_2D, unusedTexture);
        gl.texImage2D(
            gl.TEXTURE_2D,
            0,
            gl.RGBA,
            depthTextureSize,
            depthTextureSize,
            0,
            gl.RGBA,
            gl.UNSIGNED_BYTE,
            null,
        );
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);

        // 把它附加到该帧缓冲上
        gl.framebufferTexture2D(
            gl.FRAMEBUFFER,        // target
            gl.COLOR_ATTACHMENT0,  // attachment point
            gl.TEXTURE_2D,         // texture target
            unusedTexture,         // texture
            0);                    // mip level




        //let bufferInfo = WebGLUtil.createBufferInfoFromArrays(gl, arrays);
        let cameraAngleRadians = Util.degToRad(0);
        let fieldOfViewRadians = Util.degToRad(60);
        let cameraHeight = 50;
        let projectionMatrix1: number[];
        let cameraMatrix1: number[];
        let viewMatrix1: number[];
        let textureMatrix: number[];

        let updateMatrix = () => {
            // Compute the projection matrix
            let aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
            projectionMatrix1 = Matrix4.perspective(fieldOfViewRadians, aspect, 1, 2000);

            // Compute the camera's matrix using look at.
            let cameraY = this.uiParam.cameraY;
            let cameraX = this.uiParam.cameraX;
            let cameraPosition = [cameraX, cameraY, 7];
            let target = [0, 0, 0];
            let up = [0, 1, 0];
            cameraMatrix1 = Matrix4.lookAt(cameraPosition, target, up);

            let lightWorldMatrix = Matrix4.lookAt(
                [this.uiParam.posX, this.uiParam.posY, this.uiParam.posZ],          // position
                [this.uiParam.targetX, this.uiParam.targetY, this.uiParam.targetZ], // target
                [0, 1, 0],                                              // up
            );

            //textureWorldMatrix = Matrix4.multiply(textureWorldMatrix, Matrix4.scaling(this.uiParam.projWidth, this.uiParam.projHeight, 1));
            let lightProjectionMatrix: number[];
            let projWidth = this.uiParam.projWidth;
            let projHeight = this.uiParam.projHeight;
            if (this.uiParam.perspective) {
                lightProjectionMatrix = Matrix4.perspective(Util.degToRad(this.uiParam.fieldOfView), projWidth / projHeight, 0.5, 10);
            } else {
                lightProjectionMatrix = Matrix4.orthographic(-projWidth / 2, projWidth / 2, -projHeight / 2, projHeight / 2, 0.5, 10);
            }


            

            // Make a view matrix from the camera matrix.
            //viewMatrix1 = Matrix4.invert(cameraMatrix1);
            // 绘制到深度纹理
            gl.bindFramebuffer(gl.FRAMEBUFFER, depthFramebuffer);
            gl.viewport(0, 0, depthTextureSize, depthTextureSize);
            gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

            drawScene(lightProjectionMatrix, lightWorldMatrix, Matrix4.identity(),lightWorldMatrix, this.colorProgramInfo)
            gl.bindFramebuffer(gl.FRAMEBUFFER, null);
            WebGLUtil.resizeCanvasToMatchDisplaySize(gl);
            gl.clearColor(0,0,0,1);
            gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

            //裁剪坐标是-1 -> 1 纹理坐标是 0-1
            //下面是做转换
            textureMatrix = Matrix4.multiply(Matrix4.translation(0.5, 0.5, 0.5), Matrix4.scaling(0.5, 0.5, 0.5));

            textureMatrix = Matrix4.multiply(textureMatrix, lightProjectionMatrix);
            textureMatrix = Matrix4.multiply(textureMatrix, Matrix4.invert(lightWorldMatrix));
            
            drawScene(projectionMatrix1,cameraMatrix1,textureMatrix,lightWorldMatrix,this.programInfo);
            

            // 绘制立方体辅助线
            let viewMatrix = Matrix4.invert(cameraMatrix1);
            gl.useProgram(this.colorProgramInfo.program);

            // Setup all the needed attributes.
            WebGLUtil.setBuffersAndAttributes(gl, this.colorProgramInfo, cubeLinesBufferInfo);
      
            // scale the cube in Z so it's really long
            // to represent the texture is being projected to
            // infinity
            const mat = Matrix4.multiply(
                lightWorldMatrix, Matrix4.invert(lightProjectionMatrix));
      
            // Set the uniforms we just computed
            WebGLUtil.setUniforms(this.colorProgramInfo, {
              u_color: [0, 0, 0, 1],
              u_view: viewMatrix,
              u_projection: projectionMatrix1,
              u_world: mat,
            });
      
            // calls gl.drawArrays or gl.drawElements
            WebGLUtil.drawBufferInfo(gl, cubeLinesBufferInfo, gl.LINES);
        
        }

        let drawScene = (projectionMatrix: number[], cameraMatrix: number[], textureMatrix: number[],lightWorldMatrix:Number[],curProgramInfo: IProgramInfo) => {
            let time = this.ts;
            time = time * 0.5 + 5;

            let viewMatrix = Matrix4.invert(cameraMatrix);

            gl.useProgram(curProgramInfo.program);

            let bais = this.uiParam.bais;

            WebGLUtil.setUniforms(curProgramInfo, {
                u_view: viewMatrix,
                u_projection: projectionMatrix,
                u_textureMatrix: textureMatrix,
                u_projectedTexture: depthTexture,
                u_bias: bais,
                u_reverseLightDirection: lightWorldMatrix.slice(8, 11),
            });

            // Setup all the needed buffers and attributes.
            WebGLUtil.setBuffersAndAttributes(gl, curProgramInfo, sphereBufferInfo);

            // Set the uniforms that are the same for all objects.
            WebGLUtil.setUniforms(curProgramInfo, sphereUniforms);

            // Draw the geometry.
            //gl.drawArrays(gl.TRIANGLES, 0, sphereBufferInfo.numElements);
            WebGLUtil.drawBufferInfo(gl, sphereBufferInfo);

            ///平面
            // Setup all the needed buffers and attributes.
            WebGLUtil.setBuffersAndAttributes(gl, curProgramInfo, planeBufferInfo);
            // Set the uniforms that are the same for all objects.
            WebGLUtil.setUniforms(curProgramInfo, planeUniforms);
            // Draw the geometry.
            WebGLUtil.drawBufferInfo(gl, planeBufferInfo);

            ///立方体
            // Setup all the needed buffers and attributes.
            WebGLUtil.setBuffersAndAttributes(gl, curProgramInfo, cubeBufferInfo);
            // Set the uniforms that are the same for all objects.
            WebGLUtil.setUniforms(curProgramInfo, cubeUniforms);
            // Draw the geometry.
            WebGLUtil.drawBufferInfo(gl, cubeBufferInfo);

            ///画辅助线
            //gl.useProgram(this.cameraProgramInfo.program);

            //WebGLUtil.setBuffersAndAttributes(gl, this.cameraProgramInfo, cubeLinesBufferInfo);

            // // 在 Z 轴上缩放该立方体，
            // // 以便表示该纹理是被投影到无限远的。
            // //因为投影可以投到无限远的地方可以用Z轴做缩放
            // //const mat = Matrix4.multiply(textureWorldMatrix, Matrix4.scaling(1, 1, 1));
            // const mat = Matrix4.multiply(textureWorldMatrix, Matrix4.invert(textureProjectMatrix));
            // //const mat = Matrix4.identity();
            // // 设置我们计算出来的 unifroms
            // WebGLUtil.setUniforms(this.cameraProgramInfo, {
            //     u_color: [0, 0, 0, 1],
            //     u_view: viewMatrix,
            //     u_projection: projectionMatrix,
            //     u_world: mat,
            // });

            // // 调用 gl.drawArrays 或者 gl.drawElements
            // WebGLUtil.drawBufferInfo(gl, cubeLinesBufferInfo, gl.LINES);
        }
        this.draw = updateMatrix;
    }

    /* 加载图片 */
    private loadImageTexture(url: string): WebGLTexture {
        const gl = Render.GL;
        // 创建一个纹理
        const texture = gl.createTexture();
        gl.bindTexture(gl.TEXTURE_2D, texture);
        // 用一个 1x1 蓝色像素填充该纹理
        gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE,
            new Uint8Array([0, 0, 255, 255]));
        // 异步加载一张图片
        const image = new Image();
        image.src = url;
        image.addEventListener('load', function () {
            // 现在图片加载完了，把它拷贝到纹理中
            gl.bindTexture(gl.TEXTURE_2D, texture);
            gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
            // 假设该纹理的宽高是 2 的整次幂
            gl.generateMipmap(gl.TEXTURE_2D);
        });
        return texture;
    }
}
